package com.ddpt.permission.controller;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.lh.center.activity.api.dubbo.ActivityDubboService;
import com.lh.center.activity.api.entity.ActivityInfoEntity;
import com.lh.center.common.constant.RedisPrefix;
import com.lh.center.common.dto.IdDto;
import com.lh.center.common.dto.LoginByPhoneDto;
import com.lh.center.common.enums.MarketingApp;
import com.lh.center.common.exception.CommonException;
import com.lh.center.common.exception.ErrorEnum;
import com.lh.center.common.util.AssertUtil;
import com.lh.center.mall.api.dubbo.DubboMallService;
import com.lh.center.mall.api.entity.MallApplicationEntity;
import com.lh.center.merchant.api.dubbo.DubboLoginStyleService;
import com.lh.center.merchant.api.dubbo.DubboMerchantInsService;
import com.lh.center.merchant.api.dubbo.DubboMerchantService;
import com.lh.center.merchant.api.entity.LoginStyleEntity;
import com.lh.center.merchant.api.entity.Merchant;
import com.lh.center.merchant.api.entity.MerchantIns;
import com.lh.center.permission.api.request.ExternalCodeLoginReq;
import com.lh.center.permission.api.session.TingtangSessionUser;
import com.lh.center.permission.api.session.UserSessionUser;
import com.lh.center.permission.config.PermissionConfiguration;
import com.lh.center.permission.dto.ExternalLoginDto;
import com.lh.center.permission.dto.SendPhoneCodeDto;
import com.lh.center.permission.service.LoginService;
import com.lh.center.permission.service.login.LoginServiceFactory;
import com.lh.center.permission.service.login.UserLoginServiceImpl;
import com.lh.center.permission.util.IpUtil;
import com.lh.center.reach.api.dubbo.DubboExternalSmsCodeService;
import com.lh.center.reach.api.dubbo.DubboExternalUserLoginService;
import com.lh.center.reach.api.external.smsCode.beans.ExternalSmsCodeSendBean;
import com.lh.center.reach.api.external.userLogin.beans.ExternalUserLoginBean;
import com.lh.center.reach.api.external.userLogin.beans.ExternalUserLoginResultBean;
import com.lh.center.tingtang.api.dubbo.DubboTingtangInfoService;
import com.lh.center.tingtang.api.entity.TingtangInfoEntity;
import com.lh.center.user.api.dubbo.DubboUserGroupUserService;
import com.lh.center.user.api.dubbo.DubboUserService;
import com.lh.center.user.api.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 客户端用户登陆
 */
@Slf4j
@RestController
@RefreshScope
@RequestMapping("/api/permission/login/user")
public class UserLoginController {
    /**
     * 外部登录链接
     * ?loginType=external&loginParam=_$15,wechat,code,attach$_&code=1234&attach=abcdefg
     * <p>
     * 短信验证码登录链接
     * ?loginType=sms&loginParam=_$15,lianhai,default$_
     * loginParam解析
     * 1. 第一个参数为merchantId
     * 2. 第二个参数为configUnique
     * 3. 第三个参数为登录样式(有些客户会要求使用他们设计的登录页)，前端自行维护，缺少或匹配不到，则使用默认样式
     * 4. 如果以上参数不存在或不全，就使用现有样式和发送短信验证码，如果参数齐全，还是调用原来的发送短信验证码接口，但是要增加上面两个参数
     * {
     * "merchantId":"15",
     * "configUnique":"lianhai",
     * "content":"18888888888"
     * }
     */

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private PermissionConfiguration configuration;

    @DubboReference
    private DubboExternalUserLoginService dubboExternalUserLoginService;

    @DubboReference
    private DubboExternalSmsCodeService dubboExternalSmsCodeService;

    @DubboReference
    private DubboUserGroupUserService dubboUserGroupUserService;

    @DubboReference
    private DubboUserService dubboUserService;

    @Resource
    private UserLoginServiceImpl userLoginService;

    @DubboReference
    private DubboMerchantInsService dubboMerchantInsService;

    @DubboReference
    private DubboMerchantService dubboMerchantService;

    @DubboReference
    private DubboLoginStyleService dubboLoginStyleService;

    @DubboReference
    private ActivityDubboService activityDubboService;

    @DubboReference
    private DubboMallService dubboMallService;

    @DubboReference
    private DubboTingtangInfoService dubboTingtangInfoService;

    //记录发短信的号码
    private static final String cache_sms_send_phone = "cache_sms_send_phone_";

    // 根据外部免登录授权code登录
    @PostMapping("/autoLoginByCode")
    public UserSessionUser autoLoginByCode(@RequestBody ExternalCodeLoginReq req) {

        AssertUtil.isEmpty(req.getXcode(), "授权信息不能为空");

        String token = stringRedisTemplate.opsForValue().get(RedisPrefix.OPEN_USER_ACCESS_CODE + req.getXcode());

        if (StringUtils.isBlank(token)) {
            throw new CommonException("用户未登录");
        }

        String userStr = stringRedisTemplate.opsForValue().get(RedisPrefix.USER_ADMIN_USER_TOKEN + token);
        if (StringUtils.isBlank(userStr)) {
            throw new CommonException("用户未登录");
        }

        UserSessionUser user = JSONUtil.toBean(userStr, UserSessionUser.class);
        if (StringUtils.isNotBlank(req.getUnionId()) && !req.getUnionId().equals(user.getUnionId())) {
            throw new CommonException("用户未登录");
        }

        stringRedisTemplate.delete(RedisPrefix.OPEN_USER_ACCESS_CODE + req.getXcode());


        return user;
    }

    /**
     * 外部授权登陆
     */
    @PostMapping("/externalLogin")
    public UserSessionUser externalLogin(@RequestBody ExternalLoginDto dto) {
        AssertUtil.isNull(dto.getInsId(), "机构id不能为空");
        AssertUtil.isEmpty(dto.getConfigUnique(), "配置不能为空");
        AssertUtil.isEmpty(dto.getCode(), "授权信息不能为空");
        MerchantIns ins = dubboMerchantInsService.queryById(dto.getInsId());
        AssertUtil.isNull(ins, "机构不存在");
        ExternalUserLoginBean bean = new ExternalUserLoginBean();
        bean.setAttach(dto.getAttach());
        bean.setCode(dto.getCode());
        bean.setMerchantId(ins.getMerchantId());
        bean.setMerchantConfigUnique(dto.getConfigUnique());
        ExternalUserLoginResultBean login = dubboExternalUserLoginService.login(bean);
        //拿到登陆信息
        String unionId = login.getUnionId();
        String wxOpenId = login.getWxOpenId();
        if (matchLimit(unionId)) {
            throw new CommonException(configuration.getLimitMsg());
        }
        UserEntity user = dubboUserService.registerOrGetUserByUnionId(ins.getMerchantId(), unionId);
        if (user.isNewRegister() || user.getInsId() == null) {
            //补填用户其他信息
            user.setWxOpenId(wxOpenId);
            user.setRealName(login.getRealName());
            user.setHeaderImg(login.getHeaderImg());
            user.setPhone(login.getPhone());
            if (StringUtils.isNotBlank(login.getInsCode())) {
                MerchantIns query = dubboMerchantInsService.queryByUnique(ins.getMerchantId(), login.getInsCode());
                if (query != null) {
                    user.setInsId(ins.getId());
                }
            }
            if (user.getInsId() == null) {
                user.setInsId(dto.getInsId());
            }
            if (user.getInsId() == null) {
                user.setInsId(dubboMerchantInsService.getSuperInsId(ins.getMerchantId()));
            }
            dubboUserService.updateBaseInfo(user);
        }
        return userLoginService.loginSuccess(user);
    }

    //手机+验证码登陆
    @PostMapping("/loginByPhone")
    public UserSessionUser loginByPhone(@RequestBody LoginByPhoneDto dto, HttpServletRequest request) {
        String ip = IpUtil.getRealRequestIp(request);
        dto.setIp(ip);

        if (matchLimit(dto.getPhone())) {
            throw new CommonException(configuration.getLimitMsg());
        }
        LoginService<UserSessionUser> loginService = (LoginService<UserSessionUser>) LoginServiceFactory.builder(MarketingApp.user);
        return loginService.loginByPhone(dto);
    }

    //厅堂有礼手机+验证码登陆
    @PostMapping("/tingtangLoginByPhone")
    public TingtangSessionUser tingtangLoginByPhone(@RequestBody LoginByPhoneDto dto, HttpServletRequest request) {
        String ip = IpUtil.getRealRequestIp(request);
        dto.setIp(ip);

        if (matchLimit(dto.getPhone())) {
            throw new CommonException(configuration.getLimitMsg());
        }
        LoginService<TingtangSessionUser> loginService = (LoginService<TingtangSessionUser>) LoginServiceFactory.builder(MarketingApp.tingtang);
        return loginService.loginByPhone(dto);
    }

//    @PostMapping("/autoLogin")
//    public UserSessionUser autoLogin(@RequestBody LoginByPhoneDto dto) {
//        if (matchLimit(dto.getPhone())) {
//            throw new CommonException(configuration.getLimitMsg());
//        }
//        LoginService<UserSessionUser> loginService = (LoginService<UserSessionUser>) LoginServiceFactory.builder(MarketingApp.user);
//        return loginService.autoLogin(dto.getMerchantId(), dto.getPhone(), dto.getCode());
//    }

    /**
     * 发送短信验证码
     */
    @PostMapping("/sendPhoneCode")
    public void sendPhoneCode(@RequestBody SendPhoneCodeDto dto) {
        log.info("发送短信接口接收参数:{}", JSONUtil.toJsonStr(dto));
        String content = dto.getContent();
        if (!PhoneUtil.isPhone(content)) {
            throw new CommonException("手机号格式不正确");
        }

        if (matchLimit(content)) {
            throw new CommonException(configuration.getLimitMsg());
        }

        if (StringUtils.isBlank(dto.getMallCode()) && StringUtils.isBlank(dto.getActCode())&&StringUtils.isBlank(dto.getTingtangCode())){
            throw new CommonException("主体code缺失");
        }

        Merchant merchant = dubboMerchantService.getByMerchantCode(dto.getMerchantCode());
        AssertUtil.isNull(merchant, "未知商户");
        Long merchantId = merchant.getId();

        if (StringUtils.isNotBlank(dto.getMallCode())){
            MallApplicationEntity mallApplicationEntity = dubboMallService.getByCode(dto.getMallCode());

            //首先要配了短信登录的商城
            String sms = mallApplicationEntity.getLoginType();
            if (!StringUtils.equals(sms, "sms") && StringUtils.isBlank(dto.getAuthKey())) {
                throw new CommonException(ErrorEnum.not_allowed_login_type);
            }

            log.info("发送短信接口查出商城信息:{}", JSONUtil.toJsonStr(mallApplicationEntity));
            if (phoneLoginWhiteLimit(mallApplicationEntity.getPhoneLoginWhitelist(), content, merchantId)) {
                throw new CommonException(ErrorEnum.user_login_limit);
            }
            if (phoneLoginBlackLimit(mallApplicationEntity.getPhoneLoginBlacklist(), content, merchantId)) {
                throw new CommonException(ErrorEnum.user_login_limit);
            }
        } else if (StringUtils.isNotBlank(dto.getActCode())) {
            ActivityInfoEntity activityInfo = activityDubboService.getByCode(dto.getActCode());
            //首先要配了短信登录的活动
            String sms = activityInfo.getLoginType();
            if (!StringUtils.equals(sms, "sms") && StringUtils.isBlank(dto.getAuthKey())) {
                throw new CommonException(ErrorEnum.not_allowed_login_type);
            }
            log.info("发送短信接口查出活动信息:{}", JSONUtil.toJsonStr(activityInfo));
            if (phoneLoginWhiteLimit(activityInfo.getPhoneLoginWhitelist(), content, merchantId)) {
                throw new CommonException(ErrorEnum.user_login_limit);
            }
            if (phoneLoginBlackLimit(activityInfo.getPhoneLoginBlacklist(), content, merchantId)) {
                throw new CommonException(ErrorEnum.user_login_limit);
            }
        } else if (StringUtils.isNotBlank(dto.getTingtangCode())) {
            TingtangInfoEntity tingtangInfoEntity = dubboTingtangInfoService.getByCode(dto.getTingtangCode());
            Long externalSmsConfigId = tingtangInfoEntity.getExternalSmsConfigId();
            if (externalSmsConfigId<=0){
                //throw new CommonException(ErrorEnum.not_allowed_login_type);
            }
            log.info("发送短信接口查出厅堂信息:{}", JSONUtil.toJsonStr(tingtangInfoEntity));
        }

        String code = "1234";

        //发短信核心逻辑
        send:
        if (configuration.isEnablePhoneCode()) {
            String cache = stringRedisTemplate.opsForValue().get(cache_sms_send_phone + content);
            if (StringUtils.isNotBlank(cache)) {
                throw new CommonException("您的操作太频繁,请稍后再试");
            }
            code = RandomUtil.randomString("0123456789", 4);

            Long smsConfigId = null;
            //没配短信
            boolean notConfig = false;
            if (StringUtils.isNotBlank(dto.getActCode())) {
                ActivityInfoEntity activityInfo = activityDubboService.getByCode(dto.getActCode());
                //首先要配了短信登录的活动
                String sms = activityInfo.getLoginType();
                if (!StringUtils.equals(sms, "sms") && StringUtils.isBlank(dto.getAuthKey())) {
                    throw new CommonException(ErrorEnum.not_allowed_login_type);
                }

                if (activityInfo == null || activityInfo.getExternalSmsConfigId() == null || activityInfo.getExternalSmsConfigId() == -1) {
                    notConfig = true;
                } else {
                    smsConfigId = activityInfo.getExternalSmsConfigId();
                }
            }
            if (StringUtils.isNotBlank(dto.getMallCode())) {
                MallApplicationEntity mallApplicationEntity = dubboMallService.getByCode(dto.getMallCode());

                //首先要配了短信登录的商城
                String sms = mallApplicationEntity.getLoginType();
                if (!StringUtils.equals(sms, "sms") && StringUtils.isBlank(dto.getAuthKey())) {
                    throw new CommonException(ErrorEnum.not_allowed_login_type);
                }

                log.info("发送短信接口查出商城信息:{}", JSONUtil.toJsonStr(mallApplicationEntity));
                if (mallApplicationEntity == null || mallApplicationEntity.getExternalSmsConfigId() == null || mallApplicationEntity.getExternalSmsConfigId() == -1) {
                    notConfig = true;
                } else {
                    smsConfigId = mallApplicationEntity.getExternalSmsConfigId();
                }
            }

            if (StringUtils.isNotBlank(dto.getTingtangCode())) {
                TingtangInfoEntity tingtangInfoEntity = dubboTingtangInfoService.getByCode(dto.getTingtangCode());
                Long externalSmsConfigId = tingtangInfoEntity.getExternalSmsConfigId();
                if (externalSmsConfigId<=0){
                    //throw new CommonException(ErrorEnum.not_allowed_login_type);
                }
                log.info("发送短信接口查出厅堂信息:{}", JSONUtil.toJsonStr(tingtangInfoEntity));
                if (tingtangInfoEntity == null || tingtangInfoEntity.getExternalSmsConfigId() == null || tingtangInfoEntity.getExternalSmsConfigId() == -1) {
                    notConfig = true;
                } else {
                    smsConfigId = tingtangInfoEntity.getExternalSmsConfigId();
                }
            }

            if (merchantId == null || merchantId.equals(-1L) || notConfig || smsConfigId == null) {

                //系统短信发送逻辑
                if (!LoginServiceFactory.builder(MarketingApp.user).sendPhoneCode(content, code)) {
                    throw new CommonException("短信发送失败");
                }

                break send;
            }
            //调用配置发送短信
            ExternalSmsCodeSendBean bean = new ExternalSmsCodeSendBean();
            bean.setMerchantId(merchantId);
            bean.setMerchantConfigId(smsConfigId);
            bean.setPhone(content);
            bean.setSmsCode(code);
            dubboExternalSmsCodeService.send(bean);
        }

        // 发送成功缓存起来，3分钟内有效
        stringRedisTemplate.opsForValue().set(RedisPrefix.PHONE_CODE + content, code, 3, TimeUnit.MINUTES);
        //记录发短信的手机号码
        stringRedisTemplate.opsForValue().set(cache_sms_send_phone + content, "1", 1, TimeUnit.MINUTES);
    }

    /**
     * 获取登录样式配置信息
     */
    @PostMapping("/getStyle")
    public LoginStyleEntity getStyle(@RequestBody IdDto dto) {
        return dubboLoginStyleService.getById(dto.getId());
    }

    private boolean matchLimit(String unique) {
        Set<String> limitLoginUnique = configuration.getLimitLoginUnique();
        if (CollectionUtils.isEmpty(limitLoginUnique)) {
            return false;
        }
        PathMatcher matcher = new AntPathMatcher();
        return limitLoginUnique.stream().anyMatch(u -> matcher.match(u, unique));
    }


    private Boolean phoneLoginWhiteLimit(String phoneLoginWhitelist, String phone, Long merchantId) {
        if (StringUtils.isBlank(phoneLoginWhitelist)) {
            return false;
        }
        UserEntity whiteUser = getUserByUserGroupIdsAndUserPhone(phoneLoginWhitelist, phone, merchantId);
        // 用户不在白名单，限制登录。
        return ObjectUtil.isNull(whiteUser);

    }

    private Boolean phoneLoginBlackLimit(String phoneLoginBlacklist, String phone, Long merchantId) {
        UserEntity blackUser = getUserByUserGroupIdsAndUserPhone(phoneLoginBlacklist, phone, merchantId);
        // 用户在黑名单，限制登录。
        return ObjectUtil.isNotNull(blackUser);
    }

    private UserEntity getUserByUserGroupIdsAndUserPhone(String phoneLoginLimitList, String phone, Long merchantId) {
        if (StringUtils.isNotBlank(phoneLoginLimitList)) {
            List<Long> blackIds = Stream.of(phoneLoginLimitList.split(","))
                    .filter(StringUtils::isNotBlank)
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            UserEntity user = dubboUserGroupUserService.getUserByUserGroupIdsAndUserPhone(blackIds, phone, merchantId);
            return user;
        }
        return null;
    }

}
